package com.idb.jobs;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.db.DbUtil;
import cn.hutool.log.Log;
import com.idb.annotations.Path;
import com.idb.executors.IdbSqlExecutor;
import com.idb.executors.NameCmd;
import com.idb.executors.ShellExecutor;
import com.idb.utils.Helper;
import com.idb.utils.IdbSql;
import com.idb.utils.YmlCache;
import com.idb.utils.YmlUtils;
import com.idb.yml.LogYml;
import com.idb.yml.OpsYml;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.support.CronExpression;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.scheduling.support.SimpleTriggerContext;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.stream.Collectors;

@Component
public class Jobs{
    private static final Log log = Log.get();
    private static final ReentrantReadWriteLock opsLock = new ReentrantReadWriteLock();
    private static final ReentrantReadWriteLock logLock = new ReentrantReadWriteLock();

    @Autowired
    private TaskScheduler taskScheduler;

    public void start() {
        OpsYml opsYml = Optional.ofNullable(YmlCache.getYml(OpsYml.class)).orElse(new OpsYml());
        checkOps(opsYml.getOps());
        List<OpsYml.Ops> opsList = Optional.ofNullable(opsYml.getOps()).orElse(Collections.emptyList());
        List<OpsYml.Ops> executeByStarted = opsList.stream().filter(v -> "0".equals(StrUtil.emptyToDefault(v.getExecute().getCron(), "0"))).collect(Collectors.toList());
        List<OpsYml.Ops> executeByCron = opsList.stream().filter(v -> !"0".equals(StrUtil.emptyToDefault(v.getExecute().getCron(), "0"))).collect(Collectors.toList());
        for (OpsYml.Ops ops : executeByStarted) {
            boolean success = execute(ops);
            changeLog(ops.getExecute().getId(),success);
        }
        for (OpsYml.Ops ops : executeByCron) {
            this.taskScheduler.schedule(() -> {
                log.info("执行任务:{}",ops.getExecute().getId());
                boolean success = Jobs.this.execute(ops);
                this.changeLog(ops.getExecute().getId(), success);
            }, triggerContext -> {
                String cron = ops.getExecute().getCron();
                if (!CronExpression.isValidExpression(cron)) {
                    log.error(ops.getExecute().getId() + "cron表达式有误");
                }
                SimpleTriggerContext simpleTriggerContext = (SimpleTriggerContext) triggerContext;
                simpleTriggerContext.update(triggerContext.lastScheduledExecutionTime(),
                        triggerContext.lastActualExecutionTime(),
                        Convert.toDate(getOpsLog(ops.getExecute().getId()).getLastTime(),triggerContext.lastCompletionTime()));
                return new CronTrigger(cron).nextExecutionTime(simpleTriggerContext);
            });
        }
        this.createLogOnStarted();
    }

    public boolean execute(OpsYml.Ops ops) {
        try {
            String newPassword = Helper.generatePassword(ops.getExecute().getPolicy().getPrefix(), ops.getExecute().getPolicy().getSuffix());
            boolean isUseNewPass = execute(ops.getExecute(),newPassword);
            execute(ops.getAfter(),newPassword);
            if(isUseNewPass) {
                try {
                    opsLock.writeLock().lock();
                    OpsYml opsYml = Optional.ofNullable(YmlCache.getYml(OpsYml.class)).orElse(new OpsYml());
                    OpsYml.Ops newOps = opsYml.getOps().stream().filter(o -> o.getExecute().getId().equals(ops.getExecute().getId())).findFirst().get();
                    newOps.getExecute().setPassword(ops.getExecute().getPassword());
                    YmlCache.putYml(opsYml);
                    YmlUtils.dump(OpsYml.class.getAnnotation(Path.class).value(),opsYml);
                } finally {
                    opsLock.writeLock().unlock();
                }
            }
            return true;
        } catch (Exception e) {
            log.error("发生执行错误:",e);
        }
        return false;
    }

    public void createLogOnStarted() {
        OpsYml opsYml = Optional.ofNullable(YmlCache.getYml(OpsYml.class)).orElse(new OpsYml());
        List<OpsYml.Ops> opsList = Optional.ofNullable(opsYml.getOps()).orElse(Collections.emptyList());
        List<String> idList = opsList.stream().map(v -> v.getExecute().getId()).collect(Collectors.toList());
        LogYml logYml = Optional.ofNullable(YmlCache.getYml(LogYml.class)).orElse(new LogYml());
        List<LogYml.Log> logList = Optional.ofNullable(logYml.getLogs()).orElse(new ArrayList<>());
        List<String> logIdList = idList.stream().filter(id -> logList.stream().noneMatch(v -> id.equals(v.getId()))).collect(Collectors.toList());
        for (String id : logIdList) {
            LogYml.Log chLog = new LogYml.Log();
            chLog.setId(id);
            chLog.setSuccess(true);
            chLog.setLastTime(DateUtil.now());
            logList.add(chLog);
        }
        YmlCache.putYml(logYml);
        try {
            YmlUtils.dump(LogYml.class.getAnnotation(Path.class).value(),logYml);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /*
    日志持久化
     */
    public void changeLog(String id,boolean success) {
        //更新日志
        try {
            logLock.writeLock().lock();
            LogYml logYml = Optional.ofNullable(YmlCache.getYml(LogYml.class)).orElse(new LogYml());
            List<LogYml.Log> logList = Optional.ofNullable(logYml.getLogs()).orElse(new ArrayList<>());
            LogYml.Log chLog = logList.stream().filter(l -> l.getId().equals(id)).findFirst().orElse(new LogYml.Log());
            if(StrUtil.isEmpty(chLog.getId())) {
                logList.add(chLog);
            }
            chLog.setId(id);
            chLog.setSuccess(success);
            chLog.setLastTime(DateUtil.now());
            YmlCache.putYml(logYml);
            YmlUtils.dump(LogYml.class.getAnnotation(Path.class).value(),logYml);
        } catch (IOException e) {
            log.error("日志更新错误:",e);
            throw new RuntimeException(e);
        } finally {
            logLock.writeLock().unlock();
        }
    }

    private LogYml.Log getOpsLog(String id) {
        LogYml logYml = Optional.ofNullable(YmlCache.getYml(LogYml.class)).orElse(new LogYml());
        List<LogYml.Log> logList = Optional.ofNullable(logYml.getLogs()).orElse(new ArrayList<>());
        return logList.stream().filter(l -> l.getId().equals(id)).findFirst().orElse(new LogYml.Log());
    }

    public boolean execute(OpsYml.ExecuteYml executeYml,String newPassword) {
        List<String> sqls = executeYml.getSqls();
        List<String> shells = executeYml.getShells();
        AtomicBoolean isUse = new AtomicBoolean(false);
        NameCmd.Password password = new NameCmd.Password(executeYml.getPassword(), newPassword);
        if(CollectionUtil.isNotEmpty(sqls)) {
            Connection connect = null;
            try {
                connect = IdbSql.getInstance().getConnect(executeYml);
                Connection finalConnect = connect;
                sqls.forEach(sql -> {
                    NameCmd nameCmd = new NameCmd(sql, password);
                    IdbSqlExecutor.getInstance().execute(finalConnect, nameCmd.getCmd(), nameCmd.getParamMap());
                    isUse.set(isUse.get() | nameCmd.isUse());
                });
            } catch (SQLException e) {
                throw new RuntimeException(e);
            } finally {
                DbUtil.close(connect);
            }
        }
        if(CollectionUtil.isNotEmpty(shells)) {
            shells.forEach(shell -> {
                NameCmd nameCmd = new NameCmd(shell, password);
                String result = ShellExecutor.getInstance().execute(nameCmd.getCmd());
                log.info("执行Shell完成:[{}]",result);
            });
        }
        if(isUse.get() && StrUtil.isNotEmpty(executeYml.getPassword())) {
            executeYml.setPassword(password.getNewPassword());
        }
        return isUse.get();
    }

    public void checkOps(List<OpsYml.Ops> opsList) {
        Map<String, Long> collect = opsList.stream().map(v -> v.getExecute().getId()).filter(StrUtil::isNotBlank).collect(Collectors.groupingBy(id -> id, Collectors.counting()));
        if(collect.size() != opsList.size()) {
            throw new RuntimeException("execute配置ID不能为空");
        }
        List<String> idList = collect.keySet().stream().filter(key -> collect.get(key) > 1).collect(Collectors.toList());
        if(CollectionUtil.isNotEmpty(idList)) {
            String join = CollectionUtil.join(idList, ",");
            log.error("以下配置ID冲突：{}",join);
            throw new RuntimeException("以下配置ID冲突：" + join);
        }
    }
}
